Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Commit aa8723d0f400eda0182e804ca7be46c90391986d


Parents : 96fb1a4
Author : Ivan <ivan@quad4.io>
Signature : Signature validation error
Date : 2026-04-08T03:28:08-05:00

feat(workflows): add Gitea workflow to sync GitHub release assets for Windows/macOS

Changes
Diff

diff --git a/.gitea/workflows/sync-github-release-assets.yml b/.gitea/workflows/sync-github-release-assets.yml
new file mode 100644
index 00000000..882d6f9d
--- /dev/null
+++ b/.gitea/workflows/sync-github-release-assets.yml
@@ -0,0 +1,72 @@
+# Upstream GitHub (Win/mac CI artifacts): https://github.com/Sudo-Ivan/MeshChatX
+# This Gitea project (releases + API uploads): https://git.quad4.io/RNS-Things/MeshChatX
+# Secret GITEA_API_URL: API origin https://git.quad4.io (no trailing slash), same as .gitea/workflows/build.yml
+#
+# Pull Windows/macOS artifacts from GitHub Actions (.github/workflows/build-release.yml) and
+# attach them to the Gitea release for the same tag (Linux assets come from build.yml).
+#
+# On GitHub: tag push runs build-release.yml; branch pushes to "dev" only run .github/workflows/build.yml
+# (no Win/mac artifact upload).
+#
+# Repository secrets:
+# GITEA_API_URL, GITEA_TOKEN — same as .gitea/workflows/build.yml
+# GITHUB_SYNC_PAT — GitHub PAT: repo + actions:read (list/download workflow artifacts)
+#
+# Run after the Gitea "Build and Release" job has created the draft (or any release with that tag).
+name: Sync GitHub Win/mac release assets
+
+on:
+ workflow_dispatch:
+ inputs:
+ tag:
+ description: "Tag matching GitHub and Gitea (e.g. v4.4.0)"
+ required: true
+ type: string
+
+permissions:
+ contents: read
+
+env:
+ GITHUB_SYNC_REPOSITORY: Sudo-Ivan/MeshChatX
+ GITEA_SYNC_REPOSITORY: RNS-Things/MeshChatX
+ GITEA_API_URL: ${{ secrets.GITEA_API_URL }}
+
+jobs:
+ sync:
+ name: Fetch GitHub artifacts and upload to Gitea release
+ runs-on: ubuntu-latest
+ steps:
+ - name: Clone repository
+ run: |
+ set -eu
+ SERVER="${GITEA_SERVER_URL:-${GITHUB_SERVER_URL:-}}"
+ REPO="${GITEA_REPOSITORY:-${GITHUB_REPOSITORY:-}}"
+ if [ -z "$SERVER" ] || [ -z "$REPO" ]; then
+ echo "Set GITEA_SERVER_URL/GITEA_REPOSITORY or GITHUB_SERVER_URL/GITHUB_REPOSITORY" >&2
+ exit 1
+ fi
+ if [ -n "${GITEA_TOKEN:-}" ] || [ -n "${GITHUB_TOKEN:-}" ]; then
+ TOKEN="${GITEA_TOKEN:-$GITHUB_TOKEN}"
+ git config --global credential.helper "!f() { echo username=x-access-token; echo password=${TOKEN}; }; f"
+ fi
+ git clone "${SERVER}/${REPO}.git" .
+ git checkout "${GITHUB_SHA}"
+
+ - name: Sync from GitHub Actions
+ env:
+ TAG: ${{ inputs.tag }}
+ GITHUB_REPOSITORY: ${{ env.GITHUB_SYNC_REPOSITORY }}
+ GITHUB_PAT: ${{ secrets.GITHUB_SYNC_PAT }}
+ GITEA_REPOSITORY: ${{ env.GITEA_SYNC_REPOSITORY }}
+ GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
+ run: |
+ set -eu
+ if [ -z "${GITHUB_PAT:-}" ]; then
+ echo "Configure secret GITHUB_SYNC_PAT." >&2
+ exit 1
+ fi
+ if [ -z "${GITEA_API_URL:-}" ]; then
+ echo "Configure secret GITEA_API_URL (https://git.quad4.io for RNS-Things/MeshChatX)." >&2
+ exit 1
+ fi
+ bash scripts/ci/sync-github-release-assets.sh

diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml
index 93686aca..77e4ae59 100644
--- a/.github/workflows/build-release.yml
+++ b/.github/workflows/build-release.yml
@@ -1,4 +1,5 @@
-# Tagged releases from master: Windows + macOS builds.
+# Tagged releases: Windows + macOS builds (same idea as .gitea/workflows/build.yml — any tag
+# points at a commit; no requirement that the tag be on master).
#
# Pinned first-party actions (bump tag and SHA together when upgrading):
# actions/checkout@v6.0.1 8e8c483db84b4bee98b60c0593521ed34d9990e8
@@ -31,29 +32,8 @@ env:
PNPM_VERSION: "10.32.1"
jobs:
- verify-master:
- name: Verify tag on master
- runs-on: ubuntu-latest
- if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
- steps:
- - name: Checkout
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
- with:
- fetch-depth: 0
-
- - name: Ensure tagged commit is on master
- run: |
- set -euo pipefail
- git fetch origin master
- if ! git merge-base --is-ancestor "${GITHUB_SHA}" origin/master; then
- echo "Tagged commit is not an ancestor of origin/master; release tags must be cut from master." >&2
- exit 1
- fi
-
build-release:
name: Build release (${{ matrix.label }})
- needs: verify-master
- if: always() && (needs.verify-master.result == 'success' || needs.verify-master.result == 'skipped')
strategy:
fail-fast: false
matrix:

diff --git a/scripts/ci/sync-github-release-assets.sh b/scripts/ci/sync-github-release-assets.sh
new file mode 100644
index 00000000..e2a7c845
--- /dev/null
+++ b/scripts/ci/sync-github-release-assets.sh
@@ -0,0 +1,132 @@
+#!/usr/bin/env bash
+# Download Windows/macOS build artifacts from GitHub Actions (build-release workflow)
+# and attach them to an existing Gitea release. Best-effort: missing platforms are skipped.
+#
+# Required env: TAG, GITHUB_REPOSITORY, GITHUB_PAT, GITEA_API_URL, GITEA_REPOSITORY, GITEA_TOKEN
+set -euo pipefail
+
+TAG="${TAG:?set TAG to the release tag (e.g. v1.2.3)}"
+GITHUB_REPOSITORY="${GITHUB_REPOSITORY:?}"
+GITHUB_PAT="${GITHUB_PAT:?}"
+GITEA_API_URL="${GITEA_API_URL:?}"
+GITEA_REPOSITORY="${GITEA_REPOSITORY:?}"
+GITEA_TOKEN="${GITEA_TOKEN:?}"
+
+GITEA_API_URL="${GITEA_API_URL%/}"
+GH_API="https://api.github.com/repos/${GITHUB_REPOSITORY}"
+AUTH_GH=(-H "Authorization: Bearer ${GITHUB_PAT}" -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28")
+AUTH_GITEA=(-H "Authorization: token ${GITEA_TOKEN}" -H "Accept: application/json")
+
+WORKDIR=$(mktemp -d)
+trap 'rm -rf "${WORKDIR}"' EXIT
+
+enc_tag() {
+ printf '%s' "$1" | jq -sRr @uri
+}
+
+log() {
+ printf '%s\n' "$*" >&2
+}
+
+if ! command -v jq >/dev/null 2>&1; then
+ log "Error: jq is required."
+ exit 1
+fi
+
+COMMIT_SHA=""
+if COMMIT_JSON=$(curl -sS -f "${AUTH_GH[@]}" "${GH_API}/commits/$(enc_tag "$TAG")" 2>/dev/null); then
+ COMMIT_SHA=$(printf '%s' "$COMMIT_JSON" | jq -r '.sha // empty')
+fi
+
+TAG_ENC=$(enc_tag "$TAG")
+RUNS_URL="${GH_API}/actions/workflows/build-release.yml/runs?event=push&branch=${TAG_ENC}&per_page=30"
+RUN_ID=""
+if RUNS_JSON=$(curl -sS -f "${AUTH_GH[@]}" "$RUNS_URL" 2>/dev/null); then
+ RUN_ID=$(printf '%s' "$RUNS_JSON" | jq -r '
+ [.workflow_runs[] | select(.conclusion == "success")]
+ | sort_by(.created_at) | reverse | .[0].id // empty
+ ')
+fi
+
+if [ -z "$RUN_ID" ] && [ -n "$COMMIT_SHA" ]; then
+ if RUNS_JSON=$(curl -sS -f "${AUTH_GH[@]}" "${GH_API}/actions/workflows/build-release.yml/runs?per_page=50" 2>/dev/null); then
+ RUN_ID=$(printf '%s' "$RUNS_JSON" | jq -r --arg sha "$COMMIT_SHA" '
+ [.workflow_runs[] | select(.head_sha == $sha and .conclusion == "success")]
+ | sort_by(.created_at) | reverse | .[0].id // empty
+ ')
+ fi
+fi
+
+if [ -z "$RUN_ID" ]; then
+ log "No successful GitHub Actions run found for build-release.yml (tag=${TAG}). Skipping GitHub artifact sync."
+ exit 0
+fi
+
+log "Using GitHub workflow run id=${RUN_ID} for tag ${TAG}"
+
+ART_JSON=$(curl -sS "${AUTH_GH[@]}" "${GH_API}/actions/runs/${RUN_ID}/artifacts?per_page=100" || true)
+N=$(printf '%s' "${ART_JSON:-{}}" | jq -r '(.artifacts // []) | length')
+if [ "${N:-0}" -eq 0 ]; then
+ log "No artifacts on GitHub run ${RUN_ID} (or API error). Nothing to download."
+ exit 0
+fi
+
+STAGE="${WORKDIR}/stage"
+mkdir -p "$STAGE"
+
+printf '%s' "$ART_JSON" | jq -r '.artifacts[] | "\(.id)|\(.name)|\(.archive_download_url)"' | while IFS='|' read -r _art_id art_name dl_url; do
+ case "$art_name" in
+ meshchatx-windows-*|meshchatx-macos-*) ;;
+ *)
+ log "Skipping artifact with unexpected name: ${art_name}"
+ continue
+ ;;
+ esac
+ ZIP="${WORKDIR}/$(echo "$art_name" | tr '/' '_').zip"
+ log "Downloading ${art_name}..."
+ if ! curl -sS -fL "${AUTH_GH[@]}" -o "$ZIP" "$dl_url"; then
+ log "Warning: failed to download ${art_name}; continuing."
+ continue
+ fi
+ EX="${WORKDIR}/ex-${art_name}"
+ mkdir -p "$EX"
+ if ! unzip -q -o "$ZIP" -d "$EX" 2>/dev/null; then
+ log "Warning: unzip failed for ${art_name}; continuing."
+ continue
+ fi
+ find "$EX" -type f \( -name '*.exe' -o -name '*.dmg' -o -name '*.blockmap' -o -name '*.yml' -o -name '*.yaml' \) -print0 2>/dev/null \
+ | while IFS= read -r -d '' f; do
+ base=$(basename "$f")
+ cp -f "$f" "${STAGE}/${base}"
+ log "Staged ${base}"
+ done
+done
+
+STAGED_N=$(find "$STAGE" -mindepth 1 -maxdepth 1 -type f 2>/dev/null | wc -l)
+STAGED_N=${STAGED_N//[[:space:]]/}
+if [ "${STAGED_N:-0}" -eq 0 ]; then
+ log "No .exe/.dmg (or related) files extracted from GitHub artifacts. Nothing to upload."
+ exit 0
+fi
+
+REL_JSON=$(curl -sS "${AUTH_GITEA[@]}" "${GITEA_API_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases/tags/${TAG}")
+REL_ID=$(printf '%s' "$REL_JSON" | jq -r '.id // empty')
+if [ -z "$REL_ID" ] || [ "$REL_ID" = "null" ]; then
+ log "Error: No Gitea release for tag '${TAG}'. Create the release first (e.g. push the tag so .gitea/workflows/build.yml runs)."
+ exit 1
+fi
+
+log "Uploading to Gitea release id=${REL_ID} (${GITEA_REPOSITORY}@${TAG})"
+
+find "$STAGE" -maxdepth 1 -type f -print0 | while IFS= read -r -d '' f; do
+ base=$(basename "$f")
+ NAME_ENC=$(printf '%s' "$base" | jq -sRr @uri)
+ if curl -sS -f "${AUTH_GITEA[@]}" -F "attachment=@${f}" \
+ "${GITEA_API_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases/${REL_ID}/assets?name=${NAME_ENC}" >/dev/null; then
+ log "Uploaded ${base}"
+ else
+ log "Warning: upload failed or duplicate for ${base}; continuing."
+ fi
+done
+
+log "Done."


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────